﻿/*
 * The MIT License
 * 
 * Copyright 2008 Jeppe Burchardt and Pimm Hogeling
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package org.dr.actionconsole
{
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.KeyboardEvent;
	import flash.events.TimerEvent;
	import flash.net.SharedObject;
	import flash.profiler.showRedrawRegions;
	import flash.system.System;
	import flash.ui.Keyboard;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getTimer;
	import flash.utils.Timer;
	import flash.xml.XMLDocument;
	import flash.xml.XMLNode;
	import flash.xml.XMLNodeType;
	import org.dr.actionconsole.data.CommandData;
	import org.dr.actionconsole.events.ConsoleEvent;
	import org.dr.actionconsole.view.DefaultConsoleView;
	import org.dr.actionconsole.view.IConsoleView;
	
	public class Console
	{
		/**
		 * A bright white color.
		 */
		public static const BOLD : uint = 0xFFFFFF;
		/**
		 * A smooth red color that can be used for error messages.
		 */
		public static const ERROR : uint = 0xFF9999;
		/**
		 * The default white/grey color.
		 */
		public static const NORMAL : uint = 0xBBBBBB;
		private static const SYSTEM : uint = 0x99FF99;
		private static const USER_INPUT : uint = 0x3399FF;
		
		public static const DEFAULT_FONT : String = "ActionConsole default font";
		
		public static const DEFAULT_CONSOLE_VIEW : Class = DefaultConsoleView;
		
		private static const SHARED_OBJECT_NAME : String = "ActionConsole";
		
		private static const NEW_LINE : String = "\n";
		
		private static var _defaultFont : String;
		private static var _shortcutKeyCode : uint = 0x4B;
		/**
		 * Bit 0: initialized
		 * Bit 1: consoleView instantiated
		 * Bit 2: consoleView opened
		 * Bit 3: write package
		 * Bit 4: write name
		 * Bit 5: write input
		 * Bit 6: persistence
		 * Bit 7: use writebuffer
		 * Bit 8: write time before lines
		 * Bit 9: write time before events
		 * Bit 10: heuristic input event dispatching
		 * Bit 11-31: empty
		 */
		private static var bits : uint = 0x20;
		private static var commands : Array;
		private static var consoleView : IConsoleView;
		private static var consoleViewClass : Class;
		private static var eventDispatcher : EventDispatcher;
		private static var history : Array;
		private static var historyPosition : int;
		private static var listeners : Array;
		private static var memoryTimer : Timer;
		private static var shared : SharedObject;
		private static var stage : Stage;
		private static var writeBuffer : Array;
		private static var xmlIndent : uint;
		
		public function Console() : void
		{
			throw new Error("Console is a static class and should not be instantiated. See the wiki page for more details: http://code.google.com/p/actionconsole/wiki/Usage");
		}
		/**
		 * Adds a commands to the console's internal command list.
		 * 
		 * @param match	<String> This word, when typed in console, will trigger the 'listener' function.
		 * @param listener	<Function> The function that will be triggered by typing the 'match' parameter in console. If this
		 *  function accepts an Array as it's only parameter this array will contain all arguments that were inputted into the
		 *  console.
		 * @param help	<String (default = null)> A help line that is printed when 'help' command is used.
		 * @param argumentTags	<Array (default = null)> This Array should contain Strings that describe the function of all the
		 *  arguments that are expected. This will be used to print the usage when 'help' command is used.
		 * @param useWeakReference	<Boolean (default = false)> Determines whether the reference to the listener is strong or
		 *  weak. A strong reference (the default) prevents your listener from being garbage-collected. A weak reference does
		 *  not. Class-level member functions are not subject to garbage collection, so you can set useWeakReference to true for
		 *  class-level member functions without subjecting them to garbage collection. If you set useWeakReference to true for
		 *  a listener that is a nested inner function, the function will be garbage-collected and no longer persistent. If you
		 *  create references to the inner function (save it in another variable) then it is not garbage-collected and stays 
		 *  persistent.
		 */
		public static function addCommand(match:String, listener:Function, help:String = null, argumentTags:Array = null, useWeakReference:Boolean = false) : void
		{
			if (bits & 1)
			{
				commands.push(new CommandData(match, listener, help, argumentTags, useWeakReference));
			}
		}
		/**
		 * Registers an event listener object with an EventDispatcher object so that the listener
		 *  receives notification of an event. You can register event listeners on all nodes in the
		 *  display list for a specific type of event, phase, and priority.
		 *
		 * @param type	<String> The type of event.
		 * @param listener	<Function> The listener function that processes the event. This function must accept an Event
		 *  object as its only parameter and must return void.
		 * @param useCapture	<Boolean (default = false)> Determines whether the listener works in the capture phase or the
		 *  target and bubbling phases. If useCapture is set to true, the listener processes the event only during the capture
		 *  phase and not in the target or bubbling phase. If useCapture is false, the listener processes the event only during
		 *  the target or bubbling phase. To listen for the event in all three phases, call addEventListener twice, once with
		 *  useCapture set to true, then again with useCapture set to false.
		 * @param priority	<int (default = 0)> The priority level of the event listener. The priority is designated by a signed
		 *  32-bit integer. The higher the number, the higher the priority. All listeners with priority n are processed before
		 *  listeners of priority n-1. If two or more listeners share the same priority, they are processed in the order in 
		 *  which they were added. The default priority is 0.
		 * @param useWeakReference	<Boolean (default = false)> Determines whether the reference to the listener is strong or
		 *  weak. A strong reference (the default) prevents your listener from being garbage-collected. A weak reference does
		 *  not. Class-level member functions are not subject to garbage collection, so you can set useWeakReference to true for
		 *  class-level member functions without subjecting them to garbage collection. If you set useWeakReference to true for
		 *  a listener that is a nested inner function, the function will be garbage-collected and no longer persistent. If you
		 *  create references to the inner function (save it in another variable) then it is not garbage-collected and stays 
		 *  persistent.
		 */
		public static function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false) : void
		{
			if (!eventDispatcher)
			{
				eventDispatcher = new EventDispatcher();
			}
			eventDispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
		}
		/**
		 * Adds a ICommandListener object to the console so it will be used when a command is entered.
		 * 
		 * @param listener	<ICommandListener> The ICommandListener object that will be used when a command is entered.
		 * @see org.dr.actionconsole.ICommandListener
		 */
		public static function addListener(listener : ICommandListener) : void
		{
			listeners.push(listener);
		}
		/**
		 * Closes the console.
		 */
		public static function close() : void
		{
			if (bits & 4) // If the console is opened.
			{
				_close();
			}
		}
		/**
		 * The font that is used when Console.DEFAULT_FONT is passed as the font parameter to write and writeLine methods, and the font
		 *  that is used when printing messages using the writeXML and writeEvent methods where a font can not be specified.
		 */
		public static function get defaultFont() : String
		{
			return _defaultFont;
		}
		public static function set defaultFont(value:String) : void
		{
			if (value)
			{
				_defaultFont = value;
			}
		}
		/**
		 * Returns true if the console has been initialized using the initialize method, and false if not.
		 * 
		 * @return A Boolean object representing whether the console has been initialized.
		 */
		public static function getInitialized() : Boolean
		{
			return Boolean(bits & 1);
		}
		/**
		 * Determines whether the console should dispatch ConsoleEvent.INPUT events heuristic. This means that these events
		 *  will be dispatched before the console checks the internal command list and before the console uses the listeners
		 *  attached to it. When set to false (the default) the console will check the internal command list and the listeners
		 *  first when a user commits an input, and only dispatch a ConsoleEvent.INPUT event if no matching internal command
		 *  is found and all listeners returned false.
		 * 
		 * @default false
		 */
		public static function get heuristicInputEventDispatching() : Boolean
		{
			return Boolean(bits & 0x400);
		}
		public static function set heuristicInputEventDispatching(value:Boolean) : void
		{
			if (value !== heuristicInputEventDispatching)
			{
				bits ^= 0x400;
			}
		}
		/**
		 * Initializes the console so it can be opened using the keyboard binding; and the writeLine, write, writeXML and writeEvent 
		 *  methods can be used. Before this method is called the console will be useless, but will not use a lot of memory. Note that
		 *  all writeLine, write, writeXML and writeEvent method calls will do nothing if this method isn't called first. Also note 
		 *  that if you are planning on using the setConsoleView method do so before calling this method.
		 * 
		 * @param _stage	<Stage> The Stage, this is used to add the console to the screen.
		 * @param useBuffer	<Boolean (default = true)> Determines whether the console's writebuffer should be used. When this is
		 *  set to true the console willl store any data passed using the writeLine, write, writeXML and writeEvent methods in the
		 *  writebuffer so they can be written to the console when it's first opened. Setting this to false will save memory, but
		 *  any writeLine, write, writeXML and writeEvent calls will do nothing unless the console was opened first.
		 * @param writeDefaultBuffer <Boolean (default = true)> Write the default "Welcome to ActionConsole! ActionConsole is a FOSS
		 *  input/output console [...]" message to the writebuffer (even if the useBuffer parameter is set to false).
		 */
		public static function initialize(_stage:Stage, useBuffer:Boolean = true, writeDefaultBuffer:Boolean = true) : void
		{
			if (!(bits & 1) && _stage) // If not already initialized. Initializing it more than once could result in a memory leak because of the addEventListener call or in unexpected behavior from the console because some properties are set.
			{
				history = new Array();
				stage = _stage;
				if (!consoleViewClass)
				{
					consoleViewClass = DEFAULT_CONSOLE_VIEW;
				}
				commands = DEFAULT_COMMANDS;
				if (!_defaultFont)
				{
					_defaultFont = "_sans";
				}
				listeners = new Array();
				eventDispatcher = new EventDispatcher();
				if (writeDefaultBuffer)
				{
					writeBuffer = DEFAULT_WRITE_BUFFER;
				}
				else
				{
					writeBuffer = new Array();
				}
				shared = SharedObject.getLocal(SHARED_OBJECT_NAME);
				stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown, false, int.MAX_VALUE, false); // int.MAX_VALUE because the console should be the first thing to react to a down key.
				bits ^= 1;
				if (useBuffer)
				{
					bits ^= 0x80;
				}
				if (shared.data.visible == true)
				{
					_open();
				}
				memoryTimer = new Timer(0);
				memoryTimer.addEventListener(TimerEvent.TIMER, memoryTick);
			}
		}
		/**
		 * Opens the console. Note that after using this method the setConsoleView method will no longer have any effect.
		 */
		public static function open() : void
		{
			if ((bits & 5) === 1) // If the console is closed and initialized.
			{
				_open();
			}
		}
		/**
		 * Determines whether the console should store the current state of the console (opened or closed) in a SharedObject object.
		 *  If this is set to true, and the console is still opened when the Flash application is closed the console will
		 *  automatically open the next time it is initialized.
		 */
		public static function get persistence() : Boolean
		{
			return Boolean(bits & 0x40);
		}
		public static function set persistence(value:Boolean) : void
		{
			if (value !== persistence)
			{
				bits ^= 0x40;
			}
			if (!value) // If set to false.
			{
				shared.data.visible = false;
			}
		}
		/**
		 * Adds an alias to a command. Typing this alias in the console will then trigger the same listener as the original 
		 *  command does. There is no adding aliasses to unexisting commands.
		 * 
		 * @param alias	<String> This word, when typed in console, will trigger the same listener as 'match'.
		 * @param match	<String> This should be the match of an existing command, or an alias for an existing command.
		 */
		public static function registerAlias(alias:String, match:String) : void
		{
			for each (var command : CommandData in commands)
			{
				if (command.matches(match))
				{
					command.addAlias(alias);
					break;
				}
			}
		}
		/**
		 * Removes a command from the console's internal command list. Make sure the match parameter is the same as the one that
		 *  was used while adding the command. You cannot remove a command by passing an alias of that command.
		 * 
		 * @param match	<String> The same 'match' that was used when adding the command that should be removed.
		 */
		public static function removeCommand(match:String) : void
		{
			var i : int = -1;
			const l : uint = commands.length;
			while (++i < l)
			{
				if ((commands[i] as CommandData).id === match)
				{
					commands.splice(i, 1);
				}
			}
		}
		/**
		 * Removes a listener from the EventDispatcher object. If there is no matching listener registered with the EventDispatcher
		 *  object, a call to this method has no effect.
		 *
		 * @param type	<String> The type of event.
		 * @param listener	<Function> The listener object to remove.
		 * @param useCapture	<Boolean (default = false)> Specifies whether the listener was registered for the capture phase or the
		 *  target and bubbling phases. If the listener was registered for both the capture phase and the target and bubbling phases,
		 *  two calls to removeEventListener() are required to remove both, one call with useCapture set to true, and another call
		 *  with useCapture set to false.
		 */
		public static function removeEventListener(type:String, listener:Function, useCapture:Boolean=false) : void
		{
			if (!eventDispatcher)
			{
				return;
			}
			eventDispatcher.removeEventListener(type, listener, useCapture);
		}
		/**
		 * Removes a ICommandListener object that was added using the addListener method.
		 * 
		 * @param listener	<ICommandListener> The listener that has to be removed.
		 */
		public static function removeListener(listener:ICommandListener) : void
		{
			var i:int = -1;
			const l:uint = listeners.length;
			while (++i < l)
			{
				if (listeners[i] === listener)
				{
					listeners.splice(i, 1);
					return;
				}
			}
		}
		/**
		 * This method will set the class that is used to display the console when first opened. If this method is called after
		 *  the console has been opened, it will have no effect on the appearance of the console. It is recommended that you call
		 *  this method before you call the initialize method, since the initialize method might open the console automatically
		 *  because of persistence.
		 * 
		 * @param consoleView	<Class> The class that should be used to display the console. This class must implement the IConsoleView
		 *  class.
		 * @see org.dr.actionconsole.IConsoleView
		 */
		public static function setConsoleView(consoleView:Class) : void
		{
			consoleViewClass = consoleView;
		}
		/**
		 * The console will open and initialize when this keycode is pressed along with CTRL.
		 * 
		 * @default 0x4B (K button)
		 */
		public static function get shortcutKeyCode() : uint
		{
			return _shortcutKeyCode;
		}
		public static function set shortcutKeyCode(value:uint) : void
		{
			_shortcutKeyCode = value;
		}
		/**
		 * This method returns the word "ActionConsole".
		 * 
		 * @return <String> A String object containing the word "ActionConsole".
		 */
		public static function toString():String
		{
			return "ActionConsole";
		}
		/**
		 * Removes an alias from a command.
		 * 
		 * @param alias	<String> The alias that has to be removed.
		 */
		public static function unregisterAlias(alias:String) : void
		{
			for each (var command:CommandData in commands)
			{
				if (command.removeAlias(alias))
				{
					return;
				}
			}
		}
		public static function write(text:*, type:uint=NORMAL, font:String=DEFAULT_FONT) : void
		{
			_write(convertToString(text), type, font);
		}
		public static function writeEvent(e:Event) : void
		{
			if (!e)
			{
				return;
			}
			const eventString : String = e.toString();
			const eventDetails : Array = eventString.substring(1, eventString.length - 1).split(" ");
			const eventClassName : String = getQualifiedClassName(e);
			const eventClass : Class = getDefinitionByName(eventClassName) as Class;
			if (writeTimeBeforeEvents)
			{
				_write(getTime(), NORMAL, _defaultFont);
			}
			_write(eventDetails[0] + " type=", BOLD, _defaultFont);
			var details : String = "";
			var i : uint = 0;
			const l : uint = eventDetails.length;
			while (++i < l)
			{
				var detailArray : Array = eventDetails[i].split("=", 2);
				if (detailArray[0] === "type")
				{
					// Build the "caps style" type string
					const type : String = detailArray[1];
					var capsType : String = "";
					var j : uint = 0;
					const m : uint = type.length - 1;
					while (++j < m)
					{
						const char : String = type.charAt(j);
						if (char.toUpperCase() === char)
						{
							capsType += "_" + char;
						}
						else
						{
							capsType += char;
						}
					}
					if (eventClass[capsType.toUpperCase()])
					{
						// If the "caps style" type string exists in the class, we write the that string
						_write(eventClassName.split("::", 2)[1] + "." + capsType.toUpperCase(), BOLD, _defaultFont);
					}
					else
					{
						// Otherwise we write the normal string that we found in the eventString property
						_write(type, BOLD, _defaultFont);
					}
				}
				else
				{
					details += " " + eventDetails[i];
				}
			}
			_write(details + NEW_LINE, NORMAL, _defaultFont);
		}
		/**
		 * Determines whether the console should print out the input by the user directly.
		 * 
		 * @default true
		 */
		public static function get writeInput() : Boolean
		{
			return Boolean(bits & 0x20);
		}
		public static function set writeInput(value:Boolean) : void
		{
			if (value !== writeInput)
			{
				bits ^= 0x20;
			}
		}
		public static function writeLine(text:*, type:uint=NORMAL, font:String=DEFAULT_FONT) : void
		{
			var prefix : String = "";
			if (writeTimeBeforeLines)
			{
				prefix += getTime();
			}
			if (bits & 0x18)
			{
				try
				{
					try
					{
						throw new Error("Fake error");
					}
					catch (e:Error)
					{
						if (e.getStackTrace())
						{
							var stacks : Array = e.getStackTrace().split("\n")[2].substring(4).split("[")[0].split("::");
							if (bits & 8)
							{
								prefix += stacks[0];
								if (bits & 0x10)
								{
									prefix += ".".concat(stacks[1].slice(0, -2));
								}
							}
							else
							{
								prefix += stacks[1].slice(0, -2);
							}
							prefix += ": ";
						}
					}
				}
				catch (e:Error)
				{
					prefix = "Unknown: ";
				}
			}
			_write(prefix + convertToString(text) + NEW_LINE, type, font);
		}
		/**
		 * Determines whether the console should print the package of the class that calls the writeLine method.
		 * 
		 * @default false
		 */
		public static function get writeName() : Boolean
		{
			return Boolean(bits & 0x10);
		}
		public static function set writeName(value:Boolean) : void
		{
			if (value !== writeName)
			{
				bits ^= 0x10;
			}
		}
		/**
		 * Determines whether the console should print the package of the class that calls the writeLine method.
		 * 
		 * @default false
		 */
		public static function get writePackage() : Boolean
		{
			return Boolean(bits & 8);
		}
		public static function set writePackage(value:Boolean) : void
		{
			if (value !== writePackage)
			{
				bits ^= 8;
			}
		}
		/**
		 * Determines whether the console should print the time that has elepsed since Flash Player was initialized in front
		 *  of an Event object written with the writeEvent method. For time in front of messages written with the writeLine
		 *  method, see writeTimeBeforeLine.
		 * 
		 * @default false
		 */
		public static function get writeTimeBeforeEvents() : Boolean
		{
			return Boolean(bits & 0x200);
		}
		public static function set writeTimeBeforeEvents(value:Boolean) : void
		{
			if (value !== writeTimeBeforeEvents)
			{
				bits ^= 0x200;
			}
		}
		/**
		 * Determines whether the console should print the time that has elepsed since Flash Player was initialized in front
		 *  of a message written with the writeLine method. For time in front of Event objects written with the writeEvent
		 *  method, see writeTimeBeforeEvent.
		 * 
		 * @default false
		 */
		public static function get writeTimeBeforeLines() : Boolean
		{
			return Boolean(bits & 0x100);
		}
		public static function set writeTimeBeforeLines(value:Boolean) : void
		{
			if (value !== writeTimeBeforeLines)
			{
				bits ^= 0x100;
			}
		}
		public static function writeXML(xml:*) : void
		{
			var doc : XMLDocument;
			switch(getQualifiedClassName(xml))
			{
				case "String":
				case "XML":
				doc = new XMLDocument(xml);
				break;
				case "XMLList":
				doc = new XMLDocument(xml[0]);
				break;
				case "flash.xml::XMLDocument":
				doc = xml;
				break;
				default:
				return;
			}
			writeXMLNode(doc.firstChild);
			write(NEW_LINE);
		}
		private static function _close() : void
		{
			bits ^= 4;
			if (bits & 0x40)
			{
				shared.data.visible = new Boolean();
			}
			consoleView.setOpened(false);
		}
		private static function _open() : void
		{
			if (!(bits & 2)) // If the consoleView is not instantiated.
			{
				consoleView = new consoleViewClass();
				stage.addChild(consoleView.getDisplayObject());
				stage.addEventListener(Event.ADDED, siblingAdded, false, int.MIN_VALUE, false); // int.MIN_VALUE because the console should be the last thing to react to a ADDED event.
				bits ^= 2;
				var i:int = -1;
				const l:uint = writeBuffer.length;
				while (++i < l)
				{
					var b:Array = writeBuffer[i];
					_write(b[0], b[1], b[2]);
					b = null;
				}
				writeBuffer = null;
				eventDispatcher.dispatchEvent(new ConsoleEvent(ConsoleEvent.FIRST_OPENED));
			}
			bits ^= 4;
			if (bits & 0x40)
			{
				shared.data.visible = new Boolean(true);
			}
			consoleView.setOpened(true);
		}
		private static function _write(text:String, type:uint, font:String) : void
		{
			if (bits & 1) // If the console is initiated.
			{
				if (font === DEFAULT_FONT)
				{
					font = _defaultFont;
				}
				if (bits & 2) // If the consoleView is instantiated.
				{
					consoleView.write(text, type, font); // Write to console.
				}
				else if (bits & 0x80) // If the console is set to use the writebuffer.
				{
					writeBuffer.push(new Array(text, type, font)); // Write to writebuffer, will be written when the console is first opened.
				}
			}
		}
		private static function convertToString(text:*) : String
		{
			if (!text)
			{
				return "null";
			}
			switch (typeof text)
			{
				case "xml":
				return text.toXMLString();
				break;
				case "function":
				try
				{
					const f:* = text();
					if (typeof f !== "undefined")
					{
						return f.toString();
					}
				}
				catch (e:Error)
				{
				}
				break;
			}
			return text.toString();
		}
		private static function get DEFAULT_COMMANDS() : Array
		{
			var defaultCommands : Array = new Array();
			defaultCommands.push(new CommandData("clear", defaultCommandClear, "Clears the console"));
			defaultCommands.push(new CommandData("history", defaultCommandHistory, "Prints all command that are inputted into the console"));
			defaultCommands.push(new CommandData("mem", defaultCommandMem, "Prints the memory use of this application", new Array("interval in ms|\"stop\"")));
			defaultCommands.push(new CommandData("redraw", defaultCommandRedraw, "Turns redrawregions on or off", new Array("\"on\"|\"off\"")));
			defaultCommands.push(new CommandData("help", defaultCommandHelp, "Prints the help and usage of the given command", new Array("command")));
			return defaultCommands;
		}
		private static function get DEFAULT_WRITE_BUFFER() : Array
		{
			var defaultWriteBuffer : Array = new Array();
			defaultWriteBuffer.push(new Array("Welcome to ActionConsole!", BOLD));
			defaultWriteBuffer.push(new Array(" ActionConsole is a small and effective input/output console for actionscript 3. This project is released under the X11/MIT license. You can obtain a copy of the source on http://actionconsole.googlecode.com/\nCopyright 2008 Jeppe Burchardt and Pimm Hogeling\n\n", NORMAL));
			return defaultWriteBuffer;
		}
		private static function defaultCommandClear(args:Array) : void
		{
			consoleView.clearConsole();
		}
		private static function defaultCommandHelp(args:Array) : void
		{
			if (args.length) 
			{
				for each (var c:CommandData in commands)
				{
					if (c.matches(args[0]))
					{
						write(c.help + NEW_LINE);
						write("Usage: ", BOLD);
						write(c.usage + NEW_LINE);
						return;
					}
				}
				write("Unknown command" + NEW_LINE);
			}
			else
			{
				if (listeners.length)
				{
					for each (var listener:ICommandListener in listeners)
					{
						listener.printUsage();
					}
				}
				else
				{
					write("Usage:", BOLD);
					write(" help [command]" + NEW_LINE);
				}
			}
		}
		private static function defaultCommandHistory(args:Array) : void
		{
			write("History: ", BOLD);
			write(history.join(", ") + NEW_LINE);
		}
		private static function defaultCommandMem(args:Array) : void
		{
			var interval:uint;
			if (args[0] === "stop")
			{
				memoryTimer.stop();
			}
			else
			{
				try
				{
					interval = parseInt(args[0]);
				}
				catch (e:Error)
				{
				}
				finally
				{
					if (interval)
					{
						memoryTimer.delay = interval;
						memoryTimer.start();
					}
					memoryTick();
				}
			}
		}
		private static function getTime() : String
		{
			return (getTimer() / 1000).toString() + " s: ";
		}
		private static function defaultCommandRedraw(args:Array) : void
		{
			switch(args[0])
			{
				case "on":
				showRedrawRegions(true);
				break;
				case "off":
				showRedrawRegions(false);
				break;
			}
		}
		private static function handleCommand(command:String) : void
		{
			// Write input
			if (bits & 0x20)
			{
				write(">>> " + command + NEW_LINE, USER_INPUT);
			}
			
			// Event listeners in heuristic mode
			if (bits & 0x400 && eventDispatcher.hasEventListener(ConsoleEvent.INPUT))
			{
				eventDispatcher.dispatchEvent(new ConsoleEvent(ConsoleEvent.INPUT, command));
			}
			
			// Generate the pattern
			var pattern : Array = new Array();
			var target : Boolean = true;
			const split : Array = command.split("\"");
			for each (var part : String in split)
			{
				if (target = !target)
				{
					pattern.push(part);
				}
				else
				{
					const pSplit : Array = part.split(" ");
					for each (var subPart : String in pSplit)
					{
						if (subPart)
						{
							pattern.push(subPart);
						}
					}
				}
			}
			const match:String = pattern[0];
			const args:Array = pattern.slice(1);
			
			pattern = null;
			
			// Internal commands
			for each (var c:CommandData in commands)
			{
				if (c.matches(match))
				{
					const l:Function = c.listener;
					if (l !== null)
					{
						try
						{
							l(args);
						}
						catch (e:Error)
						{
							if (e.errorID === 1063) // If the error is thrown because it was an argument problem.
							{
								try
								{
									l(); // We do our best to still call the listener, so we try to do so with no arguments.
								}
								catch (r:Error)
								{
									throw e;
								}
							}
							else
							{
								throw e;
							}
						}
						return;
					}
					else // l is null, which probably means the listener is garbage collected.
					{
						removeCommand(c.id);
					}
				}
			}
			
			// Listeners
			for each (var listener:ICommandListener in listeners)
			{
				if (listener.userInput(command))
				{
					return;
				}
			}
			
			// Event listeners in non-heuristic mode
			if (!(bits & 0x400) && eventDispatcher.hasEventListener(ConsoleEvent.INPUT))
			{
				eventDispatcher.dispatchEvent(new ConsoleEvent(ConsoleEvent.INPUT, command));
			}
		}
		private static function keyDown(event:KeyboardEvent) : void
		{
			if (event.ctrlKey && event.keyCode === _shortcutKeyCode)
			{
				bits & 4 ? _close() : _open();
			}
			else if (bits & 4) // If the console if opened.
			{
				switch (event.keyCode)
				{
					case Keyboard.ENTER: // Enter key.
					const command : String = consoleView.getInput();
					if (command)
					{
						history.unshift(command);
						historyPosition = -1;
						consoleView.setInput("");;
						handleCommand(command);
					}
					break;
					case Keyboard.ESCAPE:
					_close();
					break;
					case Keyboard.UP:
					if (history.length)
					{
						historyPosition = ++ historyPosition % history.length;
						consoleView.setInput(history[historyPosition]);
					}
					break;
					case Keyboard.DOWN:
					if (history.length)
					{
						historyPosition = (-- historyPosition < 0 ? history.length-1 : historyPosition);
						consoleView.setInput(history[historyPosition]);
					}
					break;
				}
			}
		}
		private static function memoryTick(event:Event = null) : void
		{
			const mem : uint = System.totalMemory;
			write("System memory: " + String(uint(mem / 1048.576) / 1000) + " MiB", Console.BOLD);
			write(" (" + mem.toString() + " bytes)\n");
		}
		private static function siblingAdded(event:Event) : void
		{
			try
			{
				var sibling : DisplayObject = event.target as DisplayObject;
				if (stage.getChildIndex(sibling) > stage.getChildIndex(consoleView.getDisplayObject()))
				{
					stage.swapChildren(consoleView.getDisplayObject(), sibling);
				}
			}
			catch(e:Error)
			{
				
			}
		}
		private static function writeXMLNode(node:XMLNode) : void
		{
			var nodeName : int = 0xC97FFF;
			var nodeBrackets : int = BOLD;
			var nodeValue : int = NORMAL;
			var attName : int = BOLD;
			var attBrackets : int = 0x7FA1FF;
			var attValue : int = 0x7FA1FF;
			
			write(NEW_LINE);
			var t : uint = 0;
			while (t < xmlIndent)
			{
				write("\t");
				++ t;
			}
			
			write("<", nodeBrackets, "_typewriter");
			write(node.nodeName, nodeName, "_typewriter");
			for (var attributeName : String in node.attributes)
			{
				write(" " + attributeName + "=", attName, "_typewriter");
				write("\"", attBrackets, "_typewriter");
				write(node.attributes[attributeName], attValue, "_typewriter");
				write("\"", attBrackets, "_typewriter");
			}
			
			
			if (node.hasChildNodes())
			{
				var childsOnSingleLine : Boolean = (node.childNodes.length < 2);
				
				write(">", nodeBrackets, "_typewriter");
				
				for (var i : int = 0; i < node.childNodes.length; i++)
				{
					var child : XMLNode = node.childNodes[i];
					
					if (!childsOnSingleLine)
					{
						//write(NEW_LINE);
					}
					
					++ xmlIndent;
					if (child.nodeType == XMLNodeType.ELEMENT_NODE)
					{
						writeXMLNode(child);
					}
					else
					{
						write(child.nodeValue, NORMAL, "_typewriter");
					}
					-- xmlIndent;
				}
				
				if (!childsOnSingleLine)
				{
					write(NEW_LINE);
				}
				write("<", nodeBrackets, "_typewriter");
				write("/", 0xFF7F7F, "_typewriter");
				write(node.nodeName, nodeName, "_typewriter");
				write(">", nodeBrackets, "_typewriter");
				
			}
			else
			{
				write(NEW_LINE);
				write("/", 0xFF7F7F, "_typewriter");
				write(">", nodeBrackets, "_typewriter");
			}
		}
	}
}
